Conversation
86a54c0 to
fde99ab
Compare
kinetiknz
left a comment
There was a problem hiding this comment.
I haven't reviewed this thoroughly yet, but I've included some initial feedback.
The iloc read loop is potentially very expensive after these changes. It should remain the way it was, but extended to read the required items.
The read_infe/read_string changes don't work correctly for multiple reasons, so I don't believe this code has been tested.
We'll need automated tests added to cover the changes in the core parser.
| .map(|item| self.item_as_slice(item)) | ||
| } | ||
|
|
||
| pub fn spatial_extents(&self) -> Result<&ImageSpatialExtentsProperty> { |
There was a problem hiding this comment.
The duplication in these additions with the *_ptr() variants needs to be addressed.
There was a problem hiding this comment.
I am not sure how to address it, since using the existing _ptr APIs requires unsafe blocks:
error[E0133]: dereference of raw pointer is unsafe and requires unsafe function or block
--> src\codecs\avif\decoder.rs:99:21
|
99 | height: (*dimensions).image_height,
| ^^^^^^^^^^^^^ dereference of raw pointer
|
= note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior
As per #444 (comment) you've been relying on cbindgen to use this crate, so didn't want to modify them.
There was a problem hiding this comment.
fn primary_property<'a, T>(
&'a self,
box_type: BoxType,
extract: impl Fn(&'a ItemProperty) -> Option<&'a T>,
) -> Result<Option<&'a T>> {
let primary = self.primary_item.as_ref().ok_or(Error::from(Status::PitmMissing))?;
match self.item_properties.get(primary.id, box_type)? {
Some(prop) => Ok(Some(extract(prop).expect("property key mismatch"))),
None => Ok(None),
}
}The safe and _ptr variants can then be rewritten as:
pub fn image_mirror(&self) -> Result<&ImageMirror> {
self.primary_property(BoxType::ImageMirror, |p| match p {
ItemProperty::Mirroring(v) => Some(v),
_ => None,
})?
.ok_or_else(|| Status::ImirMissing.into())
}
pub fn image_mirror_ptr(&self) -> Result<*const ImageMirror> {
match self.image_mirror() {
Ok(v) => Ok(v),
Err(Error::InvalidData(Status::PitmMissing | Status::ImirMissing)) => Ok(std::ptr::null()),
Err(e) => Err(e),
}
}for example. The spatial_extents change needs to preserve the fail_with_status_if check.
| return Ok(None); | ||
| } | ||
|
|
||
| let item_name = src.read_string(strictness)?; |
There was a problem hiding this comment.
Use the pattern in read_hdlr to read nul-terminated strings. Use correct error types per that pattern, not the generic InvalidUtf8 that read_string returned.
With skip_box_remain moved, this is now broken when parsing item_type == uri boxes. Also broken if the optional content_encoding field is not present, since these changes assume it is.
There was a problem hiding this comment.
Use the pattern in read_hdlr to read nul-terminated strings.
There are four different instances of null-terminated UTF-8 strings, I would like them to not copy-paste code for each one. The next iteration turns it into a standalone function with two new error code enums, let me know if that's OK.
Also took care of the uri boxes.
if the optional
content_encodingfield is not present
In the AVIF files I have here with XMP metadata, the field is present but empty. I couldn't find in 14496:12-2020 how to detect missing-but-optional strings, could you confirm whether this is done by expecting the box to end upon read?
fde99ab to
191fd55
Compare
191fd55 to
4575afd
Compare
|
Hi, is there anything I can do to help this forward? |
I think it'd be a good idea to separate this into two PRs. It's fairly easy to review and merge the "Add safe wrappers for AVIF metadata boxes" change, the only issue with that is the need to reduce the duplication between the safe and "Read and expose Exif and XMP metadata in the AvifContext" needs tests for the |
| .map(|item| self.item_as_slice(item)) | ||
| } | ||
|
|
||
| pub fn spatial_extents(&self) -> Result<&ImageSpatialExtentsProperty> { |
There was a problem hiding this comment.
fn primary_property<'a, T>(
&'a self,
box_type: BoxType,
extract: impl Fn(&'a ItemProperty) -> Option<&'a T>,
) -> Result<Option<&'a T>> {
let primary = self.primary_item.as_ref().ok_or(Error::from(Status::PitmMissing))?;
match self.item_properties.get(primary.id, box_type)? {
Some(prop) => Ok(Some(extract(prop).expect("property key mismatch"))),
None => Ok(None),
}
}The safe and _ptr variants can then be rewritten as:
pub fn image_mirror(&self) -> Result<&ImageMirror> {
self.primary_property(BoxType::ImageMirror, |p| match p {
ItemProperty::Mirroring(v) => Some(v),
_ => None,
})?
.ok_or_else(|| Status::ImirMissing.into())
}
pub fn image_mirror_ptr(&self) -> Result<*const ImageMirror> {
match self.image_mirror() {
Ok(v) => Ok(v),
Err(Error::InvalidData(Status::PitmMissing | Status::ImirMissing)) => Ok(std::ptr::null()),
Err(e) => Err(e),
}
}for example. The spatial_extents change needs to preserve the fail_with_status_if check.
| } | ||
| } | ||
|
|
||
| pub fn colour_information(&self) -> Result<&ColourInformation> { |
There was a problem hiding this comment.
Don't add this as-is. item_properties.get() errors with Status::IprpConflict when multiple colr boxes are associated with the primary item but both an nclx and an ICC colr on one item is legal per HEIF (and common in real AVIFs that want to specify both display primaries and a profile). Add safe variants of the existing nclx_colour_information_ptr and use icc_colour_information directly.
| { | ||
| Some(ItemProperty::Mirroring(imir)) => Ok(imir), | ||
| Some(other_property) => panic!("property key mismatch: {:?}", other_property), | ||
| None => Err(Error::from(Status::ItemTypeMissing)), |
There was a problem hiding this comment.
Error type should be Status::ImirMissing.
| { | ||
| Some(ItemProperty::PixelAspectRatio(pasp)) => Ok(pasp), | ||
| Some(other_property) => panic!("property key mismatch: {:?}", other_property), | ||
| None => Err(Error::from(Status::ItemTypeMissing)), |
There was a problem hiding this comment.
Error type should be Status::PaspMissing.
| struct PropertyIndex(u16); | ||
| #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] | ||
| struct ItemId(u32); | ||
| pub struct ItemId(u32); |
There was a problem hiding this comment.
Why was this and ItemInfoEntry made pub?
| item_type: u32, | ||
| pub struct ItemInfoEntry { | ||
| pub item_id: ItemId, | ||
| pub item_type: u32, |
| }; | ||
|
|
||
| assert!(item.is_none()); | ||
| let mut item: Option<AvifItem> = None; |
There was a problem hiding this comment.
Revert these changes entirely. This can be done more efficiently with:
Before the iloc_items loop:
let exif_item_id = item_infos.iter()
.find(|i| i.item_type.to_be_bytes() == *b"Exif")
.map(|i| i.item_id);
let xmp_item_id = item_infos.iter()
.find(|i| i.item_type.to_be_bytes() == *b"mime"
&& i.content_type.as_ref()
.is_some_and(|ct| ct.as_slice() == b"application/rdf+xml"))
.map(|i| i.item_id);Inside the loop:
let item = if Some(item_id) == primary_item_id {
&mut primary_item
} else if Some(item_id) == alpha_item_id {
&mut alpha_item
} else if Some(item_id) == exif_item_id {
&mut exif_metadata
} else if Some(item_id) == xmp_item_id {
&mut xmp_metadata
} else {
continue;
};
Hi all,
This PR adds support for a few BMFF boxes that are required to speed up the AVIF decoder's setup in image-rs, avoiding having to decode the full image to query e.g. dimensions, bit depth, etc.
While at it, I've also added support for detecting and exposing the Exif and XMP metadata boxes.
All feedback is appreciated.
Fixes #441
Fixes #444
cc @Shnatsel